import { _decorator, Node, UITransform, Widget, size, Size } from 'cc';
import { IBaseController } from "db://app/base/BaseController";
import BaseView from "db://app/base/BaseView";
import Core from "db://app/Core";
import { GComponent } from "db://fairygui-xforge/FairyGUI";
import { UIPanel } from "db://fairygui-xforge/UIPanel";
const { property, disallowMultiple } = _decorator;

type ComponentClass<T> = (new () => T) & { URL: string };

type IPick<T> = {
    -readonly [P in keyof T]: T[P] extends Function
    ? T[P]
    : (T[P] extends Object
        ? IPick<T[P]>
        : T[P]);
};

export type IControllerInstance<C, T extends { [key in string]: any }> = IPick<C> & Readonly<{
    /**获取第一个事件回调的返回值 */
    emit<K extends keyof T>(key: K, ...args: Parameters<T[K]>): void;
    /**发射事件 */
    call<K extends keyof T & keyof T>(key: K, ...args: Parameters<T[K]>): ReturnType<T[K]>;
    /**注册事件回调 */
    on<K extends keyof T>(key: K, callback: (...args: Parameters<T[K]>) => ReturnType<T[K]>, target?: any): void;
    /**注册一次性事件回调 */
    once<K extends keyof T>(key: K, callback: (...args: Parameters<T[K]>) => ReturnType<T[K]>, target?: any): void;
    /**取消事件回调 */
    off(key: keyof T, callback: Function, target?: any): void;
    /**取消事件回调 */
    targetOff(target: any): void;
}>

interface IFairyGUIPop extends GComponent {
    onUIInit(): void;
    onUIUpdate(params: any): void;
    onUIHide(): void;
    onUIDestroy(): void;
}

export interface IFairyInnerPopData {
    cls: PopImpl<any, any>,
    params?: any,
    onShow?: () => void,
    onHide?: () => void,
    onError?: (e: any) => void
}

type PopImpl<TP extends (TG & IFairyGUIPop), TG extends GComponent> = ComponentClass<TP>;

abstract class FairyGUIView<T extends GComponent> extends BaseView {
    @property(UIPanel)
    panel: UIPanel;

    protected Content: T;

    private _isDestroyed: boolean;

    showPop<TP extends (TG & IFairyGUIPop), TG extends GComponent>(popCls: PopImpl<TP, TG>
        , params?: any, onShow?: ()=>void, onHide?: ()=>void, onError?: (err: any) => void
    ): TP {
        return this.panel.root.showPop(popCls, params, onShow, onHide, onError);
    }

    hidePop<TP extends (TG & IFairyGUIPop), TG extends GComponent>(popInst?: TP | string): void {
        this.panel.root.hidePop(popInst);
    }

    showPops(pops: IFairyInnerPopData[], onComplete?: ()=>void) {
        const task = Core.inst.lib.task.createSync();
        if (pops.length === 0) {
            return task.start(onComplete);
        }

        pops.forEach((pop) => {
            task.add((next) => {
                this.showPop(pop.cls, pop.params, pop.onShow, ()=>{
                    pop.onHide && pop.onHide();
                    next();
                }, (err: any)=>{
                    console.error(`${pop.cls} show error: ${err}`);
                    pop.onError && pop.onError(err);
                    next();
                });
            });
        });

        return task.start(onComplete);
    }

    onLoad(): void {
        this.node.on(UIPanel.Events.ON_UIPANEL_PRE_DESTROY, this.onNodeDestroy, this);
    }

    protected beforeShow(next: (error?: string) => void, data?: any) {
        if (this.Content) {
            next && next(null);
            return;
        }

        if (this.panel) {
            this.panel.loadPromise.then(() => {
                this.onPanelLoaded(next);
            })
                .catch(next);
        } else {
            next && next("panel not found");
        }
    }

    protected beforeHide(data?: any): string | void {
        this.onNodeDestroy();
        return null;
    }

    private onPanelLoaded(next: (err?: string) => void): void {
        if (this._isDestroyed) {
            next && next("view is destroyed");
            return;
        }
        this.Content = this.panel.Component as T;
        this.onUIInit(this.Content);
        next && next(null);
    }

    protected onUIInit(content: T): void {

    }

    protected onUIUpdate(content: T, params: any): void {

    }

    protected onUIDestroy(content: T): void {

    }

    onShow(params: any): void {
        this.onUIUpdate(this.Content, params);
    }

    private onNodeDestroy(): void {
        this.node.off(UIPanel.Events.ON_UIPANEL_PRE_DESTROY, this.onNodeDestroy, this);
        this._isDestroyed = true;
        this.onUIDestroy(this.Content);
        this.Content = null;
    }

    protected onDestroy(): void {

    }
}

export function FairyPop<TG extends GComponent, C, T extends { [key in string]: any }>(cls: ComponentClass<TG>, Controller?: IBaseController<C, T>) {
    @disallowMultiple()
    //@ts-ignore
    class FairyGUIPopImpl extends cls implements IFairyGUIPop {
        onUIInit(): void { };
        onUIUpdate(params: any): void { };
        onUIHide(): void { };
        onUIDestroy(): void { };

        hide(includeAllAbove?: boolean): void {
            this.Root.hidePopup(this, !includeAllAbove);
        }

        protected get controller(): C {
            return Controller ? Controller.inst as any as C : null;
        }
    }

    return FairyGUIPopImpl;
}

export function FairyView<TG extends GComponent, C, T extends { [key in string]: any }>(cls: ComponentClass<TG>, Controller?: IBaseController<C, T>) {
    @disallowMultiple()
    class FairyGUIViewImpl extends FairyGUIView<TG> {
        public static get URL(): string {
            return cls.URL;
        }

        protected get controller(): IControllerInstance<C, T> {
            return Controller ? Controller.inst as any as IControllerInstance<C, T> : null;
        }
    }

    return FairyGUIViewImpl;
}

export function FairyScriptView<TG extends GComponent, C, T extends { [key in string]: any }>(packageName: string, itemName: string, Controller?: IBaseController<C, T>) {
    @disallowMultiple()
    class FairyGUIViewImpl extends FairyGUIView<TG> {
        public static get URL(): string {
            return `ui://${packageName}/${itemName}`;
        }

        public static createUINode(parent: Node): Node {
            const parentTransform = parent.getComponent(UITransform);
            const size = parentTransform && parentTransform.contentSize || new Size(100, 100);

            const root = new Node();
            root.active = false;
            const rootTrans = root.addComponent(UITransform);
            rootTrans.setContentSize(size);
            const widget = root.addComponent(Widget);
            widget.isAlignLeft = widget.isAlignRight = widget.isAlignTop = widget.isAlignBottom = true;
            widget.left = widget.right = widget.top = widget.bottom = 0;

            const node = new Node();
            node.parent = root;
            const nodeTrans = node.addComponent(UITransform);
            nodeTrans.setContentSize(size);
            const subWidget = node.addComponent(Widget);
            subWidget.isAlignLeft = subWidget.isAlignRight = subWidget.isAlignTop = subWidget.isAlignBottom = true;
            subWidget.left = subWidget.right = subWidget.top = subWidget.bottom = 0;

            const panel = node.addComponent(UIPanel);
            panel.packageName = packageName;
            panel.itemName = itemName;

            const impl = root.addComponent(this);
            impl.panel = panel;
            return root;
        }

        protected get controller(): IControllerInstance<C, T> {
            return Controller ? Controller.inst as any as IControllerInstance<C, T> : null;
        }
    }
    return FairyGUIViewImpl;
}
